package db

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"strings"
	"time"

	"github.com/LyricTian/retry"
	"gopkg.in/gorp.v2"

	// 引入mysql驱动
	_ "github.com/go-sql-driver/mysql"
)

// M 定义字典
type M map[string]interface{}

// DB MySQL数据库
type DB struct {
	*gorp.DbMap
	cfg *Config
}

// Config 数据库配置参数
type Config struct {
	DSN          string        // 连接串
	Trace        bool          // 打印日志
	MaxLifetime  time.Duration // 链接的最大生命周期
	MaxOpenConns int           // 最大连接数
	MaxIdleConn  int           // 最大空闲连接数
}

// NewDB 创建MySQL数据库实例
func NewDB(cfg *Config) (*DB, error) {
	if cfg == nil {
		return nil, fmt.Errorf("缺少MySQL配置文件")
	}

	if cfg.MaxIdleConn == 0 {
		cfg.MaxIdleConn = 50
	}

	if cfg.MaxOpenConns == 0 {
		cfg.MaxOpenConns = 150
	}

	if cfg.MaxLifetime == 0 {
		cfg.MaxLifetime = time.Hour * 2
	}

	m := &DB{
		cfg: cfg,
	}

	db, err := sql.Open("mysql", m.cfg.DSN)
	if err != nil {
		return nil, err
	}

	// 尝试发送Ping包
	err = retry.DoFunc(3, func() error {
		perr := db.Ping()
		if perr != nil {
			fmt.Println("发送ping值错误：", perr.Error())
		}
		return perr
	}, func(i int) time.Duration {
		return time.Second
	})
	if err != nil {
		return nil, err
	}

	db.SetMaxIdleConns(m.cfg.MaxIdleConn)
	db.SetMaxOpenConns(m.cfg.MaxIdleConn)
	db.SetConnMaxLifetime(m.cfg.MaxLifetime)

	m.DbMap = &gorp.DbMap{Db: db, Dialect: gorp.MySQLDialect{Encoding: "UTF8"}}

	if m.cfg.Trace {
		m.TraceOn("[mysql]", log.New(os.Stdout, "", log.Lmicroseconds))
	}

	return m, nil
}

// Close 关闭数据库连接
func (m *DB) Close() {
	if m.DbMap == nil {
		return
	}
	m.Db.Close()
}

// UpdateByPK 更新表数据
func (m *DB) UpdateByPK(table string, pk, info M) (int64, error) {
	q := fmt.Sprintf("UPDATE %s SET", table)

	var (
		cols []string
		vals []interface{}
	)

	for k, v := range info {
		cols = append(cols, fmt.Sprintf("%s=?", k))
		vals = append(vals, v)
	}

	q = fmt.Sprintf("%s %s", q, strings.Join(cols, ","))
	cols = nil

	for k, v := range pk {
		cols = append(cols, fmt.Sprintf("%s=?", k))
		vals = append(vals, v)
	}

	q = fmt.Sprintf("%s WHERE %s", q, strings.Join(cols, " and "))

	result, err := m.Exec(q, vals...)
	if err != nil {
		return 0, err
	}

	affected, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}

	return affected, nil
}

// DeleteByPK 删除表数据
func (m *DB) DeleteByPK(table string, pk M) (int64, error) {
	q := fmt.Sprintf("DELETE FROM %s", table)

	var (
		cols []string
		vals []interface{}
	)

	for k, v := range pk {
		cols = append(cols, fmt.Sprintf("%s=?", k))
		vals = append(vals, v)
	}

	q = fmt.Sprintf("%s WHERE %s", q, strings.Join(cols, " and "))
	result, err := m.Exec(q, vals...)
	if err != nil {
		return 0, err
	}

	affected, err := result.RowsAffected()
	if err != nil {
		return 0, err
	}

	return affected, nil
}

// GetWhereValue where 条件
func GetWhereValue(filter M) (where string, value []interface{}) {

	var (
		w []string
	)
	value = make([]interface{}, 0, len(filter))
	for k, v := range filter {
		w = append(w, fmt.Sprintf("%s=?", k))
		value = append(value, v)
	}
	where = fmt.Sprintf(" WHERE %s", strings.Join(w, " AND "))
	return
}

// GetLimitOffset 获取limit offset
func GetLimitOffset(index, size int) (offset int) {
	offset = (index - 1) * size
	return
}
